/**
 * Author(s): xuming.Wong <xuming.Wong@gmail.com>
 */

#include "PrimitiveManager.h"

#include "GameMaterialManager.h"

namespace PQEngine
{
	const std::string PrimitiveManager::SPHERE_MESH_NAME="Geometry/Primitive/Sphere";
	const std::string PrimitiveManager::CUBE_MESH_NAME="Geometry/Primitive/Cube";
	const std::string PrimitiveManager::CYLINDER_MESH_NAME="Geometry/Primitive/Cylinder";

	const std::string PrimitiveManager::GIZMO_MESH_TRANSLATE="Geometry/Primitive/Gizmo/Translate";
	const std::string PrimitiveManager::GIZMO_MESH_ROTATE="Geometry/Primitive/Gizmo/Rotate";
	const std::string PrimitiveManager::GIZMO_MESH_SCALE="Geometry/Primitive/Gizmo/Scale";

	PrimitiveManager::PrimitiveManager()
	{
		init();
	}

	PrimitiveManager& PrimitiveManager::getSingleton()
	{
		static PrimitiveManager inst;
		return inst;
	}

	void PrimitiveManager::init()
	{
		createSphereMesh(SPHERE_MESH_NAME,1, 64, 64);
		createTransformGizmoMesh();
	}

	//only call once
	void PrimitiveManager::createCubeMesh(Ogre::String name, Ogre::String matName)
	{
		Ogre::ManualObject* cube = new Ogre::ManualObject(name);
		cube->begin(matName);

		cube->position(0.5,-0.5,1.0);cube->normal(0.408248,-0.816497,0.408248);cube->textureCoord(1,0);
		cube->position(-0.5,-0.5,0.0);cube->normal(-0.408248,-0.816497,-0.408248);cube->textureCoord(0,1);
		cube->position(0.5,-0.5,0.0);cube->normal(0.666667,-0.333333,-0.666667);cube->textureCoord(1,1);
		cube->position(-0.5,-0.5,1.0);cube->normal(-0.666667,-0.333333,0.666667);cube->textureCoord(0,0);
		cube->position(0.5,0.5,1.0);cube->normal(0.666667,0.333333,0.666667);cube->textureCoord(1,0);
		cube->position(-0.5,-0.5,1.0);cube->normal(-0.666667,-0.333333,0.666667);cube->textureCoord(0,1);
		cube->position(0.5,-0.5,1.0);cube->normal(0.408248,-0.816497,0.408248);cube->textureCoord(1,1);
		cube->position(-0.5,0.5,1.0);cube->normal(-0.408248,0.816497,0.408248);cube->textureCoord(0,0);
		cube->position(-0.5,0.5,0.0);cube->normal(-0.666667,0.333333,-0.666667);cube->textureCoord(0,1);
		cube->position(-0.5,-0.5,0.0);cube->normal(-0.408248,-0.816497,-0.408248);cube->textureCoord(1,1);
		cube->position(-0.5,-0.5,1.0);cube->normal(-0.666667,-0.333333,0.666667);cube->textureCoord(1,0);
		cube->position(0.5,-0.5,0.0);cube->normal(0.666667,-0.333333,-0.666667);cube->textureCoord(0,1);
		cube->position(0.5,0.5,0.0);cube->normal(0.408248,0.816497,-0.408248);cube->textureCoord(1,1);
		cube->position(0.5,-0.5,1.0);cube->normal(0.408248,-0.816497,0.408248);cube->textureCoord(0,0);
		cube->position(0.5,-0.5,0.0);cube->normal(0.666667,-0.333333,-0.666667);cube->textureCoord(1,0);
		cube->position(-0.5,-0.5,0.0);cube->normal(-0.408248,-0.816497,-0.408248);cube->textureCoord(0,0);
		cube->position(-0.5,0.5,1.0);cube->normal(-0.408248,0.816497,0.408248);cube->textureCoord(1,0);
		cube->position(0.5,0.5,0.0);cube->normal(0.408248,0.816497,-0.408248);cube->textureCoord(0,1);
		cube->position(-0.5,0.5,0.0);cube->normal(-0.666667,0.333333,-0.666667);cube->textureCoord(1,1);
		cube->position(0.5,0.5,1.0);cube->normal(0.666667,0.333333,0.666667);cube->textureCoord(0,0);

		cube->triangle(0,1,2);      cube->triangle(3,1,0);
		cube->triangle(4,5,6);      cube->triangle(4,7,5);
		cube->triangle(8,9,10);      cube->triangle(10,7,8);
		cube->triangle(4,11,12);   cube->triangle(4,13,11);
		cube->triangle(14,8,12);   cube->triangle(14,15,8);
		cube->triangle(16,17,18);   cube->triangle(16,19,17);
		cube->end();

		cube->convertToMesh(CUBE_MESH_NAME,Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
		delete cube;
	}


	//only call once
	void PrimitiveManager::createSphereMesh(std::string meshName,float radius, int nRings, int nSegments)
	{
		Ogre::MeshPtr pSphere = Ogre::MeshManager::getSingleton().createManual(meshName, Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
		Ogre::SubMesh *pSphereVertex = pSphere->createSubMesh();

		pSphere->sharedVertexData = new Ogre::VertexData();
		Ogre::VertexData* vertexData = pSphere->sharedVertexData;

		// define the vertex format
		Ogre::VertexDeclaration* vertexDecl = vertexData->vertexDeclaration;
		size_t currOffset = 0;
		// positions
		vertexDecl->addElement(0, currOffset, Ogre::VET_FLOAT3, Ogre::VES_POSITION);
		currOffset += Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3);
		// normals
		vertexDecl->addElement(0, currOffset, Ogre::VET_FLOAT3,  Ogre::VES_NORMAL);
		currOffset += Ogre::VertexElement::getTypeSize(Ogre::VET_FLOAT3);
		// two dimensional texture coordinates
		vertexDecl->addElement(0, currOffset, Ogre::VET_FLOAT2,  Ogre::VES_TEXTURE_COORDINATES, 0);
		currOffset += Ogre::VertexElement::getTypeSize( Ogre::VET_FLOAT2);

		// allocate the vertex buffer
		vertexData->vertexCount = (nRings + 1) * (nSegments+1);
		Ogre::HardwareVertexBufferSharedPtr vBuf = Ogre::HardwareBufferManager::getSingleton().createVertexBuffer(vertexDecl->getVertexSize(0), vertexData->vertexCount, Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY, false);
		Ogre::VertexBufferBinding* binding = vertexData->vertexBufferBinding;
		binding->setBinding(0, vBuf);
		float* pVertex = static_cast<float*>(vBuf->lock(Ogre::HardwareBuffer::HBL_DISCARD));

		// allocate index buffer
		pSphereVertex->indexData->indexCount = 6 * nRings * (nSegments + 1);
		pSphereVertex->indexData->indexBuffer = Ogre::HardwareBufferManager::getSingleton().createIndexBuffer(Ogre::HardwareIndexBuffer::IT_16BIT, pSphereVertex->indexData->indexCount, Ogre::HardwareBuffer::HBU_STATIC_WRITE_ONLY, false);
		Ogre::HardwareIndexBufferSharedPtr iBuf = pSphereVertex->indexData->indexBuffer;
		unsigned short* pIndices = static_cast<unsigned short*>(iBuf->lock(Ogre::HardwareBuffer::HBL_DISCARD));

		float fDeltaRingAngle = (Ogre::Math::PI / nRings);
		float fDeltaSegAngle = (2 * Ogre::Math::PI / nSegments);
		unsigned short wVerticeIndex = 0 ;

		// Generate the group of rings for the sphere
		for( int ring = 0; ring <= nRings; ring++ ) {
			float r0 = radius * sinf (ring * fDeltaRingAngle);
			float y0 = radius * cosf (ring * fDeltaRingAngle);

		 // Generate the group of segments for the current ring
		 for(int seg = 0; seg <= nSegments; seg++) {
			 float x0 = r0 * sinf(seg * fDeltaSegAngle);
			 float z0 = r0 * cosf(seg * fDeltaSegAngle);

			 // Add one vertex to the strip which makes up the sphere
			 *pVertex++ = x0;
			 *pVertex++ = y0;
			 *pVertex++ = z0;

			 Ogre::Vector3 vNormal =  Ogre::Vector3(x0, y0, z0).normalisedCopy();
			 *pVertex++ = vNormal.x;
			 *pVertex++ = vNormal.y;
			 *pVertex++ = vNormal.z;

			 *pVertex++ = (float) seg / (float) nSegments;
			 *pVertex++ = (float) ring / (float) nRings;

			 if (ring != nRings) {
				 // each vertex (except the last) has six indices pointing to it
				 *pIndices++ = wVerticeIndex + nSegments + 1;
				 *pIndices++ = wVerticeIndex;               
				 *pIndices++ = wVerticeIndex + nSegments;
				 *pIndices++ = wVerticeIndex + nSegments + 1;
				 *pIndices++ = wVerticeIndex + 1;
				 *pIndices++ = wVerticeIndex;
				 wVerticeIndex ++;
			 }
		 } // end for seg
		} // end for ring

		// Unlock
		vBuf->unlock();
		iBuf->unlock();
		// Generate face list
		pSphereVertex->useSharedVertices = true;

		// the original code was missing this line:
		pSphere->_setBounds(Ogre::AxisAlignedBox(Ogre::Vector3(-radius, -radius, -radius), Ogre::Vector3(radius, radius, radius) ), false );
		pSphere->_setBoundingSphereRadius(radius);
		 // this line makes clear the mesh is loaded (avoids memory leaks)
		pSphere->load();
		
		pSphereVertex->setMaterialName(GNgMaterialManager.getDefaultMaterialName());
	}

	
	void PrimitiveManager::createTransformGizmoMesh()
	{
		createTranslateGizmoMesh();
		createRotateGizmoMesh();
		createScaleGizmoMesh();
	}

	void PrimitiveManager::createTranslateGizmoMesh()
	{
		Ogre::ManualObject* obj = new Ogre::ManualObject("GizmoMeshTranslate");

		//horizontal line
		obj->begin(GNgMaterialManager.getDefaultMaterialName(), Ogre::RenderOperation::OT_LINE_LIST);
		obj->position(0, 0, 0);
		obj->position(3, 0, 0);
		obj->index(0);
		obj->index(1);
		obj->end();
		//arrow
		createTranslateArrow(obj);

		obj->convertToMesh(GIZMO_MESH_TRANSLATE, Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);

		delete obj;
	}

	void PrimitiveManager::createTranslateArrow(Ogre::ManualObject *obj)
	{
		float const radius = 0.22f;
		float const accuracy = 8;
		obj->begin(GNgMaterialManager.getDefaultMaterialName(), Ogre::RenderOperation::OT_TRIANGLE_LIST);
		obj->position(2.85f, 0, 0);

		for(float theta = 0; theta < 2 * Ogre::Math::PI; theta += Ogre::Math::PI / accuracy) {
			obj->position(2.95f, radius * cos(theta), radius * sin(theta));
		}
		obj->position(3.45f, 0, 0);

		for(int inside = 1;inside < 16;inside++){
			obj->index(0);
			obj->index(inside);
			obj->index(inside + 1);
		}
		obj->index(0);
		obj->index(16);
		obj->index(1);

		for(int outside = 1;outside < 16;outside++){
			obj->index(17);
			obj->index(outside);
			obj->index(outside + 1);
		}
		obj->index(17);
		obj->index(16);
		obj->index(1);
		obj->end();
	}

	void PrimitiveManager::createRotateGizmoMesh()
	{
		Ogre::ManualObject* obj = new Ogre::ManualObject("GizmoMeshTranslate");
		
		obj->begin(GNgMaterialManager.getDefaultMaterialName(), Ogre::RenderOperation::OT_LINE_STRIP);

		float division = (Ogre::Math::PI / 2.0f) / 16.0f;
		float start = division * 3;
		float end = division * 14;
		int index_pos = 0;
		for(float theta = start; theta < end; theta += division) {
			obj->position(0, 3.0f * cos(theta), 3.0f * sin(theta));
			obj->index(index_pos++);
		}
		obj->end();
		
		createRotateArrow(obj);
		obj->convertToMesh(GIZMO_MESH_ROTATE, Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
		delete obj;

	}

	void PrimitiveManager::createRotateArrow(Ogre::ManualObject *obj)
	{
		float const radius = 0.22f;
		float const accuracy = 8;
		float division = (Ogre::Math::PI / 2.0f) / 16.0f;
		float start = division * 3;
		float end = division * 14;
		obj->begin(GNgMaterialManager.getDefaultMaterialName(), Ogre::RenderOperation::OT_TRIANGLE_LIST);

		Ogre::Quaternion q1,q2;
		q1.FromAngleAxis(Ogre::Degree(-90), Ogre::Vector3(0,0,1));
		q2.FromAngleAxis(Ogre::Degree(90), Ogre::Vector3(0,1,0));
	    
		Ogre::Vector3 translate1(0, 3.0f * cos(end), 3.0f * sin(end));
		Ogre::Vector3 translate2(0, 3.0f * cos(start), 3.0f * sin(start) - 0.25f);

		Ogre::Vector3 pos(-0.3f,     0,     0);
		obj->position(q1 * pos + translate1);

		for(float theta = 0; theta < 2 * Ogre::Math::PI; theta += Ogre::Math::PI / accuracy) {
			pos = Ogre::Vector3(-0.3f, radius * cos(theta), radius * sin(theta));
			obj->position(q1 * pos + translate1);
		}
		pos = Ogre::Vector3(0.3f, 0 , 0);
		obj->position(q1 * pos + translate1);

		pos = Ogre::Vector3(-0.3f,     0,     0);
		obj->position(q2 * pos + translate2);

		for(float theta = 0; theta < 2 * Ogre::Math::PI; theta += Ogre::Math::PI / accuracy) 
		{
			pos = Ogre::Vector3(-0.3f, radius * cos(theta), radius * sin(theta));
			obj->position(q2 * pos + translate2);
		}
		pos = Ogre::Vector3(0.3f, 0 , 0);
		obj->position(q2 * pos + translate2);

		for(int inside = 1;inside < 16;inside++)
		{
			obj->index(0);
			obj->index(inside);
			obj->index(inside + 1);
		}
		obj->index(0);
		obj->index(16);
		obj->index(1);

		for(int outside = 1;outside < 16;outside++)
		{
			obj->index(17);
			obj->index(outside);
			obj->index(outside + 1);
		}
		obj->index(17);
		obj->index(16);
		obj->index(1);
	    
		for(int inside = 19;inside < 34;inside++)
		{
			obj->index(18);
			obj->index(inside);
			obj->index(inside + 1);
		}
		obj->index(18);
		obj->index(34);
		obj->index(19);

		for(int outside = 19;outside < 34;outside++)
		{
			obj->index(35);
			obj->index(outside);
			obj->index(outside + 1);
		}
		obj->index(35);
		obj->index(34);
		obj->index(19);

		obj->end();
	}

	void PrimitiveManager::createScaleGizmoMesh()
	{
		float const radius = 0.22f;
		float const accuracy = 8;
		Ogre::ManualObject *obj = new Ogre::ManualObject("GizmoMeshScale");
		//horizontal line
		obj->begin(GNgMaterialManager.getDefaultMaterialName(), Ogre::RenderOperation::OT_LINE_LIST);
		obj->position(0, 0, 0);
		obj->position(2.85f, 0, 0);
		obj->index(0);
		obj->index(1);
		obj->end();

		obj->begin(GNgMaterialManager.getDefaultMaterialName(), Ogre::RenderOperation::OT_TRIANGLE_LIST);
		obj->position(2.85f,  0.20f,  0.20f);
		obj->position(2.85f,  0.20f, -0.20f);
		obj->position(2.85f, -0.20f, -0.20f);
		obj->position(2.85f, -0.20f,  0.20f);
		obj->position(3.25f,  0.20f,  0.20f);
		obj->position(3.25f,  0.20f, -0.20f);
		obj->position(3.25f, -0.20f, -0.20f);
		obj->position(3.25f, -0.20f,  0.20f);

		obj->index(0);
		obj->index(1);
		obj->index(2);
		obj->index(0);
		obj->index(2);
		obj->index(3);

		obj->index(4);
		obj->index(5);
		obj->index(6);
		obj->index(4);
		obj->index(6);
		obj->index(7);

		obj->index(0);
		obj->index(4);
		obj->index(7);
		obj->index(0);
		obj->index(7);
		obj->index(3);

		obj->index(1);
		obj->index(5);
		obj->index(6);
		obj->index(1);
		obj->index(6);
		obj->index(2);

		obj->index(0);
		obj->index(4);
		obj->index(5);
		obj->index(0);
		obj->index(5);
		obj->index(1);

		obj->index(3);
		obj->index(2);
		obj->index(6);
		obj->index(3);
		obj->index(6);
		obj->index(7);

		obj->end();
		obj->convertToMesh(GIZMO_MESH_SCALE, Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);
		delete obj;
	}

}
